package com.ds.lens.data.service.metric;

import com.google.common.collect.Lists;
import com.ds.lens.data.common.apmapi.ApmMetricClient;
import com.ds.lens.data.common.apmapi.enums.APMFilterTypeEnum;
import com.ds.lens.data.common.apmapi.in.ConditionInBO;
import com.ds.lens.data.common.apmapi.in.Page;
import com.ds.lens.data.common.apmapi.in.TransactionNameApiInBO;
import com.ds.lens.data.common.apmapi.out.ApmMetricOutVO;
import com.ds.lens.data.common.apmapi.out.InstanceBO;
import com.ds.lens.data.common.apmapi.out.TransactionNameApiBO;
import com.ds.lens.data.common.apmapi.out.TransactionNameBO;
import com.ds.lens.data.common.asserts.Asserts;
import com.ds.lens.data.common.client.vo.LensMetricInVO;
import com.ds.lens.data.common.constant.AggregatorEnum;
import com.ds.lens.data.common.constant.Constants;
import com.ds.lens.data.common.query.MQLVariable;
import com.ds.lens.data.common.util.HttpServiceClient;
import com.ds.lens.data.common.util.LensMQLBuilder;
import com.ds.lens.data.common.util.MapUtils;
import com.ds.lens.data.common.util.StartEndTimeRange;
import com.ds.lens.data.service.dao.LensIntegrationDao;
import com.ds.lens.data.service.dao.LensMetricDao;
import com.ds.lens.data.service.dao.entity.LensIntegration;
import com.ds.lens.data.service.dao.entity.LensMetric;
import com.ds.lens.data.vo.out.v2.integration.LensIntegrationDetailOutVO;
import com.ds.lens.data.vo.out.v2.metric.LensMetricNamesOutVO;
import com.ds.lens.data.vo.out.v2.monitor.LensMonitorFilterOutVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service(Constants.METRIC_SERVICE_PREFIX + Constants.APM)
public class ApmMetricQueryServiceImpl implements MetricQueryService {

    private static final String APM_INTEGRATION_NAME = Constants.APM;

    private APMQueryAdaptor apmQueryAdaptor;
    @Autowired
    private LensIntegrationDao lensIntegrationDao;

    @Autowired
    LensMetricDao lensMetricDao;

    @PostConstruct
    public void init() {
        try {
            LensIntegration lensIntegration = lensIntegrationDao.selectByName(APM_INTEGRATION_NAME);

            apmQueryAdaptor = new APMQueryAdaptor(lensIntegration.getUrl());
            apmQueryAdaptor.connect();
        } catch (Exception e) {
            log.error("error occurred.", e);
        }
    }

    @PreDestroy
    public void cleanup() {
        try {
            if (apmQueryAdaptor != null) {
                apmQueryAdaptor.close();
            }
        } catch (Exception e) {
            log.error("An error occurred.", e);
        }
    }

    @Override
    public List<Map<String, Object>> query(LensMetricInVO lensMetricInVO, LensIntegrationDetailOutVO integrationDetailOutVO, MQLVariable... mqlVariables) {
        String sql = LensMQLBuilder.buildQueryDSL(lensMetricInVO.getMetricName(),
                AggregatorEnum.valueOf(lensMetricInVO.getMetricAggr()).name(),
                integrationDetailOutVO.getName(),
                MapUtils.merge(new HashMap<>(), lensMetricInVO.getScope(), lensMetricInVO.getMetricFilter()),
                null,
                lensMetricInVO.getMetricBy(),
                new StartEndTimeRange(lensMetricInVO.getStartTime(), lensMetricInVO.getEndTime()),
                lensMetricInVO.getInterval(),
                lensMetricInVO.getGroupLimit());
        try {
            return apmQueryAdaptor.query(sql, mqlVariables);
        } catch (Exception e) {
            log.error("An error occurred. while query sql: " + sql, e);
            return null;
        }
    }

    @Override
    public List<String> getGroupByFields(LensIntegrationDetailOutVO integrationDetailOutVO, Long applicationId, String metricName) {
        if (null != integrationDetailOutVO && APM_INTEGRATION_NAME.equals(integrationDetailOutVO.getName())) {
            List<String> result = Lists.newArrayListWithCapacity(2);
            for (APMFilterTypeEnum apmFilterTypeEnum : APMFilterTypeEnum.values()) {
                result.add(apmFilterTypeEnum.getValue());
            }
            return result;
        }
        return Collections.emptyList();
    }

    @Override
    public List<LensMetricNamesOutVO> getMetricNames(LensIntegrationDetailOutVO integrationDetailOutVO, long applicationId) {
        Asserts.assertNotNull(integrationDetailOutVO, Constants.ErrorCode.NO_INTEGRATION_EXCEPTION);
        LensMetric condition = new LensMetric();
        condition.setIntegrationId(integrationDetailOutVO.getId());
        List<LensMetric> lensMetrics = lensMetricDao.queryMetricsByCondition(condition);
        if (!CollectionUtils.isEmpty(lensMetrics)) {
            return lensMetrics.stream().map(lensMetric -> {
                LensMetricNamesOutVO outVO = new LensMetricNamesOutVO();
                BeanUtils.copyProperties(lensMetric, outVO);
                return outVO;
            }).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    @Override
    public List<LensMonitorFilterOutVO> getFilters(LensIntegrationDetailOutVO integrationDetailOutVO, Long applicationId, String tierId, String metricName) {
        Asserts.assertNotEmpty(tierId, Constants.ErrorCode.PARAM_ERROR, "tierId can not be empty.");
        Asserts.assertNotNull(integrationDetailOutVO, Constants.ErrorCode.NO_INTEGRATION_EXCEPTION);
        ApmMetricClient metricClient = HttpServiceClient.getRestClient(ApmMetricClient.class, integrationDetailOutVO.getUrl());

        List<LensMonitorFilterOutVO> result = new ArrayList<>();
        TransactionNameApiInBO apiInBO = new TransactionNameApiInBO();
        apiInBO.setTierId(tierId);
        Page page = new Page();
        page.setIndex(0);
        page.setSize(200);
        ConditionInBO<TransactionNameApiInBO> conditionInBO = new ConditionInBO<>();
        conditionInBO.setCondition(apiInBO);
        conditionInBO.setPage(page);
        try {
            List<TransactionNameBO> transactionNameArray;
            ApmMetricOutVO<TransactionNameApiBO> transactionNames = metricClient.getTransactionListByAppInfo(conditionInBO);
            if (transactionNames.getCode() == 0 && transactionNames.getData() != null) {
                transactionNameArray = transactionNames.getData().getTransactionNameArray();
                if (!CollectionUtils.isEmpty(transactionNameArray)) {
                    for (TransactionNameBO nameBO : transactionNameArray) {
                        LensMonitorFilterOutVO outVO = new LensMonitorFilterOutVO();
                        outVO.setId(nameBO.getTransName());
                        outVO.setType(APMFilterTypeEnum.TRANSACTION_NAME.getKey());
                        outVO.setValue(nameBO.getTransName());
                        outVO.setDesc(APMFilterTypeEnum.TRANSACTION_NAME.getKey() + Constants.COLON + nameBO.getTransName());
                        result.add(outVO);
                    }
                }
            }
            ApmMetricOutVO<List<InstanceBO>> instances = metricClient.getInstancesByTierId(tierId);
            if (instances.getCode() == 0) {
                List<InstanceBO> instanceBOS = instances.getData();
                if (!CollectionUtils.isEmpty(instanceBOS)) {
                    instanceBOS.forEach(instanceBO -> {
                        LensMonitorFilterOutVO outVO = new LensMonitorFilterOutVO();
                        outVO.setId(instanceBO.getName());
                        outVO.setType(APMFilterTypeEnum.INSTANCE_NAME.getKey());
                        outVO.setValue(instanceBO.getName());
                        outVO.setDesc(APMFilterTypeEnum.INSTANCE_NAME.getKey() + Constants.COLON + instanceBO.getName());
                        result.add(outVO);
                    });
                }
            }
        } catch (Exception e) {
            log.error("Error acquiring data from APM.", e);
        }
        return result;
    }
}
